﻿//
// XN4SL - Xna Framework for Silverlight.
//
using Xn4Sl.Framework;
using System;
using System.IO;
using System.Windows.Media.Imaging;
using System.Xml;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;

namespace Xn4Sl.Framework.Graphics
{
    /// <summary>
    /// Represents a 2D grid of texels.
    /// </summary>
    public class Texture2D : Texture
    {
        /// <summary>
        /// Creates an uninitialized Texture2D resource of the specified dimensions.
        /// </summary>
        /// <param name="graphicsDevice">The GraphicsDevice used to display the texture.</param>
        /// <param name="width">The width of the texture, in pixels. This value must be a power of 
        ///     two if the GraphicsDeviceCapabilities.TextureCaps.RequiresPower2 property of graphicsDevice
        ///     is true. If this value is 0, a value of 1 is used.</param>
        /// <param name="height">The height of the texture, in pixels. This value must be a power of 
        ///     two if the GraphicsDeviceCapabilities.TextureCaps.RequiresPower2 property of graphicsDevice
        ///     is true. If this value is 0, a value of 1 is used.</param>
        public Texture2D(GraphicsDevice graphicsDevice, int width, int height)
        {
            this.Init(graphicsDevice, width, height);
        }

        /// <summary>
        /// Initializes image texture.
        /// </summary>
        /// <param name="graphicsDevice">The GraphicsDevice used to display the texture.</param>
        /// <param name="width">The width, in pixels, of the texture.</param>
        /// <param name="height">The height, in pixels, of the texture.</param>
        private void Init(GraphicsDevice graphicsDevice, int width, int height)
        {
            //
            // Create image transformations.
            //
            TransformGroup tg = new TransformGroup();
            tg.Children.Add(this.scale);
            tg.Children.Add(this.position);

            //
            // Set image properties.
            //
            this.image.RenderTransform = tg;
            this.image.Visibility = System.Windows.Visibility.Collapsed;
            this.image.Width = this.width = width;
            this.image.Height = this.height = height;

            this.graphicsDevice = graphicsDevice;
            this.graphicsDevice.UI.Children.Add(this.image);
        }

        /// <summary>
        /// Creates a texture resource from an image source, specifying the width and height of 
        /// the texture in pixels.
        /// </summary>
        /// <param name="graphicsDevice">The graphics device to use to create the texture resource.</param>
        /// <param name="imageSource">The image source.</param>
        /// <param name="width">The width, in pixels, of the texture.</param>
        /// <param name="height">The height, in pixels, of the texture.</param>
        private Texture2D(GraphicsDevice graphicsDevice, ImageSource imageSource, int width, int height)
        {
            //
            // Create an image.
            //
            this.image = new Image();
            this.image.Source = imageSource;
            this.Init(graphicsDevice, width, height);
        }

        /// <summary>
        /// Creates a texture resource from a file stream.
        /// </summary>
        /// <param name="graphicsDevice">The graphics device to use to create the texture resource.</param>
        /// <param name="stream">The filestream containing the texture.</param>
        /// <returns>The texture resource that has been created on the specified graphics device.</returns>
        public static Texture2D FromFile(GraphicsDevice graphicsDevice, Stream stream)             
        {
            if (stream == null) throw new Exception("Empty file stream!");

            byte[] hdr = new byte[24];

            if (stream.Read(hdr, 0, 24) < 24) throw new Exception("Invalid texture file size!");

            byte[] png_hdr = { 0x89, 0x50, 0x4E, 0x47,      // _PNG
                               0x0D, 0x0A, 0x1A, 0x0A,      
                               0x00, 0x00, 0x00, 0x0D, 
                               0x49, 0x48, 0x44, 0x52 };    // IHDR

            //
            // Check if file starts with supported byte sequence.
            //
            for (int i = 0; i < 16; i++)
            {
                if (hdr[i] != png_hdr[i])
                {
                    throw new NotSupportedException("Invalid file format (only PNG is supported)!");
                }
            }

            int width = hdr[18] * 0x100 + hdr[19];
            int height = hdr[22] * 0x100 + hdr[23];

            return new Texture2D(graphicsDevice, stream, width, height);
        }

        private Texture2D(GraphicsDevice graphicsDevice, Stream stream, int width, int height)            
        {
            //
            // Create an image from stream.
            //
            this.image = new Image();
            BitmapImage bi = new BitmapImage();
            bi.SetSource(stream);
            this.image.Source = bi;

            this.Init(graphicsDevice, width, height);
        }

        /// <summary>
        /// Clones the texture.
        /// </summary>
        /// <returns></returns>
        internal Texture2D Clone()
        {
            return new Texture2D(this.GraphicsDevice, this.image.Source, this.width, this.height);
        }

        /// <summary>
        /// Gets the pixel format of this texture resource.
        /// </summary>
        public SurfaceFormat Format
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Gets the width of this texture resource, in pixels.
        /// </summary>
        private int width;
        public int Width
        {
            get
            {
                return this.width;
            }
        }

        /// <summary>
        /// Gets the height of this texture resource, in pixels.
        /// </summary>
        private int height;
        public int Height
        {
            get
            {
                return this.height;
            }
        }

        /// <summary>
        /// Gets the state of the related TextureUsage enumeration.
        /// </summary>
        public TextureUsage TextureUsage
        {
            get
            {
                return TextureUsage.None;
            }
        }

        /// <summary>
        /// Position of the Texture.
        /// </summary>
        private TranslateTransform position = new TranslateTransform();
        internal System.Windows.Point Position 
        {
            get
            {
                return new System.Windows.Point(this.position.X, 
                                                this.position.Y);
            }
            set
            {
                this.position.X = value.X;
                this.position.Y = value.Y;
            }
        }

        /// <summary>
        /// Scale of the texture.
        /// </summary>
        private ScaleTransform scale = new ScaleTransform();
        internal System.Windows.Point Scale 
        {
            set
            {
                this.scale.ScaleX = value.X;
                this.scale.ScaleY = value.Y;
            }
        }

        /// <summary>
        /// Position offset of the image.
        /// </summary>
        internal System.Windows.Point Origin
        {
            set
            {
                this.image.RenderTransformOrigin =
                    new System.Windows.Point(value.X / this.image.Width,
                                             value.Y / this.image.Height);
            }
        }

        /// <summary>
        /// Opacity value for the texture.
        /// </summary>
        internal double Opacity 
        {
            get
            {
                return this.image.Opacity;
            }
            set
            {
                this.image.Opacity = value;
            }
        }

        /// <summary>
        /// ZIndex order of the texture.
        /// </summary>
        internal int ZIndex
        {
            get
            {
                return Canvas.GetZIndex(image);
            }
            set
            {
                Canvas.SetZIndex(image, value);
            }
        }

        /// <summary>
        /// Clipping rectangle.
        /// </summary>
        private Rectangle clip = Rectangle.Empty;
        internal Rectangle Clip 
        {
            get
            {
                return this.clip;
            }
            set
            {
                this.clip = value;

                this.image.Clip = new RectangleGeometry() 
                        { 
                            Rect = new Rect(value.X, 
                                            value.Y, 
                                            value.Width, 
                                            value.Height) 
                        };
            }
        }

        /// <summary>
        /// Gets the GraphicsDevice associated with this Texture2D.
        /// </summary>
        private GraphicsDevice graphicsDevice;
        public override GraphicsDevice GraphicsDevice
        {
            get
            {
                return this.graphicsDevice;
            }
        }

        /// <summary>
        /// Releases the unmanaged resources used by the Texture2D and optionally releases
        /// the managed resources.
        /// </summary>
        /// <param name="__p1">true to release both managed and unmanaged resources; false to release only
        //     unmanaged resources.</param>
        protected override void Dispose(bool __p1)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Retrieves a string representation of this object.
        /// </summary>
        /// <returns>String representation of this object.</returns>
        public override string ToString() 
        {
            return this.Name;
        }
    }
}
